package com.wuyu.ibatis.binding;

import com.wuyu.ibatis.exception.MybatisException;
import com.wuyu.ibatis.session.SqlSession;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;

public class MapperProxy<T> implements InvocationHandler, Serializable {

    private static final long serialVersionUID = -4724728412955527868L;

    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethodInvoker> methodCache;

    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            //如果是Object中定义的方法，直接执行。如toString(),hashCode()等
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            } else {
                return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
            }
        } catch (Throwable t) {
            throw new MybatisException(t);
        }
    }

    /**
     * 缓存调用者
     * @param method
     * @return
     * @throws Throwable
     */
    private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
        try {
            MapperMethodInvoker invoker = methodCache.get(method);
            if (invoker != null) {
                return invoker;
            }
            return methodCache.computeIfAbsent(method, m -> new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())));
        } catch (RuntimeException re) {
            Throwable cause = re.getCause();
            throw cause == null ? re : cause;
        }
    }

}
